package com.huangyi.adminservice.map.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.github.benmanes.caffeine.cache.Cache;
import com.huangyi.adminapi.config.domain.dto.ArgumentDTO;
import com.huangyi.adminapi.config.feign.ArgumentFeignClient;
import com.huangyi.adminapi.map.constants.MapConstants;
import com.huangyi.adminapi.map.domain.dto.LocationReqDTO;
import com.huangyi.adminapi.map.domain.dto.PlaceSearchReqDTO;
import com.huangyi.adminservice.map.domain.dto.*;
import com.huangyi.adminservice.map.domain.entity.SysRegion;
import com.huangyi.adminservice.map.mapper.RegionMapper;
import com.huangyi.adminservice.map.service.IMapProvider;
import com.huangyi.adminservice.map.service.IMapService;
import com.huangyi.commoncache.utils.CacheUtil;
import com.huangyi.commoncore.domain.dto.BasePageDTO;
import com.huangyi.commoncore.utils.BeanCopyUtil;
import com.huangyi.commoncore.utils.PageUtil;
import com.huangyi.commonredis.service.RedisService;
import com.huangyi.commonredis.service.RedissonLockService;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.redisson.api.RLock;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

/**
 * 地图服务层接口的实现类
 */
@Slf4j
@Service
public class MapServiceImpl implements IMapService {

    /**
     * sys_region表的mapper
     */
    @Autowired
    private RegionMapper regionMapper;
    /**
     * Redis服务对象
     */
    @Autowired
    private RedisService redisService;
    /**
     * 本地内存服务对象
     */
    @Autowired
    private Cache<String, Object> caffeineCache;
    /**
     * 分布式锁服务对象
     * 用于在多节点环境下控制缓存构建等操作的并发，防止缓存击穿或重复构建
     */
    @Autowired
    private RedissonLockService redissonLockService;

    @Autowired
    private ArgumentFeignClient argumentFeignClient;

    @Value("${map.hotCityIds}")
    private String hotCityIds;

    /**
     * 腾讯服务的服务类
     */
    @Autowired
    private IMapProvider mapProvider;
    /**
     * 在服务启动时就把数据缓存好
     */
    @PostConstruct
    public void initCityMap() {
        try {
            buildCityCache();
            buildRegionChildrenCache();
        } catch (Exception e) {
            log.error("初始化城市缓存失败", e);
        }
    }


    /**
     * 构建 parentId → children 缓存
     */
    private void buildRegionChildrenCache() {
        String lockKey = "lock:" + MapConstants.CACHE_MAP_CITY_CHILDREN_KEY;
        RLock lock = null;
        try {
            lock = redissonLockService.acquire(lockKey, 5000);
            if (lock == null) {
                log.info("其他节点正在构建 children 缓存，本节点等待使用已构建数据");
                TimeUnit.MILLISECONDS.sleep(200);
                return;
            }

            log.info("开始构建 children 缓存...");
            //读取数据
            List<SysRegion> allRegions = regionMapper.selectAllRegion();

            Map<Long, List<SysRegionDTO>> childrenMap = new HashMap<>();

            for (SysRegion region : allRegions) {
                Long pid = region.getParentId();
                // 只有有父级的节点（市/区/县…）才参与 “parentId -> children” 的分组
                if (pid != null) {
                    SysRegionDTO regionDTO = new SysRegionDTO();
                    BeanCopyUtil.copyProperties(region, regionDTO);
                    //不管新建还是已有，computeIfAbsent 都会返回对应的 List，然后把当前节点追加进去。
                    childrenMap
                            .computeIfAbsent(pid, k -> new ArrayList<>())
                            .add(regionDTO);
                }
            }

            // 批量写入缓存（每个 parentId 一个 key）
            for (Map.Entry<Long, List<SysRegionDTO>> entry : childrenMap.entrySet()) {
                String key = MapConstants.CACHE_MAP_CITY_CHILDREN_KEY + entry.getKey();
                CacheUtil.setL2Cache(redisService, key, entry.getValue(), caffeineCache, 120L, TimeUnit.SECONDS);
            }

            log.info("children 缓存构建完成，共 {} 个 parentId", childrenMap.size());

        } catch (Exception e) {
            log.error("构建 children 缓存失败", e);
        } finally {
            if (lock != null) {
                redissonLockService.releaseLock(lock);
            }
        }
    }


    /**
     * 介绍：
     *  1. 构建城市缓存（支持分布式锁，防止缓存击穿/并发数据库访问）
     *  2. 保证同一时间只有一个节点执行数据库查询构建缓存
     *  3. 其他未获得锁的节点短暂等待，再直接读取已构建好的缓存
     *  4. 缓存使用二级缓存（Redis + Caffeine），减少热点数据反复查库
     * 功能：
     *  1. 先尝试获取分布式锁（5秒过期）
     *  2. 如果未获取到锁 → 等待200ms → 返回缓存内容
     *  3. 如果获取到锁 → 查询数据库 → 写入 Redis 和 Caffeine → 返回数据
     *
     * @return 城市列表缓存数据
     */
    private void buildCityCache(){
        String lockKey = "lock:" + MapConstants.CACHE_MAP_CITY_KEY;
        RLock lock = null;
        try {
            lock = redissonLockService.acquire(lockKey, 5000);
            if (lock == null) {
                log.info("其他节点正在构建缓存，本节点等待使用已构建数据");
                // 等待一点时间让缓存生效
                TimeUnit.MILLISECONDS.sleep(200);
                return;
            }
            //开始上锁构建缓存
            log.info("开始构建城市缓存...");

            List<SysRegion> allRegions = regionMapper.selectAllRegion();

            //构建 getCityList() 本地缓存、城市列表
            List<SysRegionDTO> cityList = new ArrayList<>();
            //构建 getCityPinYinList() 本地缓存、城市列表拼音分组
            //TreeMap 排序速度快
            Map<String, List<SysRegionDTO>> cityPinYinList = new TreeMap<>();

            for (SysRegion sysRegion : allRegions) {
                if(sysRegion.getLevel().equals(MapConstants.CITY_LEVEL)){
                    SysRegionDTO dto = new SysRegionDTO();
                    // Bean 拷贝类使对象完成从 SysRegion 到 SysRegionDTO 的转换
                    BeanCopyUtil.copyProperties(sysRegion, dto);
                    //城市列表可以直接使用 SysRegionDTO，而根据拼音分组则需要多加业务逻辑
                    cityList.add(dto);
                    String firstChar = dto.getPinyin().toUpperCase().substring(0, 1);
                    if(cityPinYinList.containsKey(firstChar)){
                        cityPinYinList.get(firstChar).add(dto);
                    }else {
                        List<SysRegionDTO> list1 = new ArrayList<>();
                        list1.add(dto);
                        cityPinYinList.put(firstChar, list1);
                    }
                }
            }

            // getCityList() 设置到二级缓存
            CacheUtil.setL2Cache(redisService, MapConstants.CACHE_MAP_CITY_KEY,
                    cityList, caffeineCache, 120L, TimeUnit.SECONDS);
            // getCityPinYinList() 设置到二级缓存
            CacheUtil.setL2Cache(redisService,MapConstants.CACHE_MAP_CITY_PINYIN_KEY
                    ,cityPinYinList,caffeineCache,120L, TimeUnit.SECONDS);

            log.info("缓存构建完成：城市 {} 条，拼音分组 {} 条", cityList.size(), cityPinYinList.size());

        } catch (Exception e) {
            log.error("构建城市缓存失败", e);
        } finally {
            if (lock != null) {
                redissonLockService.releaseLock(lock);
            }
        }
    }




    /**
     * 城市列表查询
     * @return 缓存预热后的城市列表
     */
    @Override
    public List<SysRegionDTO> getCityList() {
        List<SysRegionDTO> cache  = CacheUtil.getL2Cache(redisService, MapConstants.CACHE_MAP_CITY_KEY
                , new TypeReference<List<SysRegionDTO>>() {},caffeineCache);
        if (cache != null && !cache.isEmpty()) {
            return cache;
        }
        // 缓存没有，构建并返回
        buildCityCache();
        return CacheUtil.getL2Cache(redisService, MapConstants.CACHE_MAP_CITY_KEY
                , new TypeReference<List<SysRegionDTO>>() {},caffeineCache);
    }

    /**
     * 拼音归类的城市查询 缓存预热
     * @return 缓存预热、拼音分组后的城市列表
     */
    @Override
    public Map<String, List<SysRegionDTO>> getCityPyList() {
        Map<String,List<SysRegionDTO>> cache = CacheUtil.getL2Cache(redisService, MapConstants.CACHE_MAP_CITY_PINYIN_KEY
                , new TypeReference<Map<String,List<SysRegionDTO>>>() {},caffeineCache);
        if (cache != null && !cache.isEmpty()) {
            return cache;
        }
        buildCityCache();
        return CacheUtil.getL2Cache(redisService, MapConstants.CACHE_MAP_CITY_PINYIN_KEY
                , new TypeReference<Map<String,List<SysRegionDTO>>>() {},caffeineCache);
    }


    /**
     * 根据父级区域ID获取子级区域列表
     * @param parentId 父级区域ID
     * @return 子级区域列表
     */
    @Override
    public List<SysRegionDTO> getRegionChildren(Long parentId) {
        // 1. parentId 参与缓存构造
        String key = MapConstants.CACHE_MAP_CITY_CHILDREN_KEY + parentId;
        // 2. 查缓存
        List<SysRegionDTO> cacheList = CacheUtil.getL2Cache(redisService, key
                , new TypeReference<List<SysRegionDTO>>() {},caffeineCache);
        // 3. 判断是否有缓存
        if(cacheList != null && !cacheList.isEmpty()) {
            return cacheList;
        }
        //构建 parentId → children 缓存
        buildRegionChildrenCache();

        return CacheUtil.getL2Cache(redisService, key, new TypeReference<List<SysRegionDTO>>() {}, caffeineCache);
    }

    /**
     * 获取热门城市列表
     * @return 返回热门城市列表
     */
    @Override
    public List<SysRegionDTO> getCityHostList() {
        List<SysRegionDTO> hotCityList = CacheUtil.getL2Cache(redisService, MapConstants.CACHE_MAP_HOT_CITY
                , new TypeReference<List<SysRegionDTO>>() {},caffeineCache);
        if(hotCityList != null && !hotCityList.isEmpty()) {
            return hotCityList;
        }

        // argumentFeignClient 参数配置服务远程调用接口
        ArgumentDTO sysHotCity = argumentFeignClient.getByConfigKey(MapConstants.CONFIG_KEY);
        if (sysHotCity == null || sysHotCity.getValue() == null) {
            return Collections.emptyList(); // 没有配置就返回空
        }
        String hotCity = sysHotCity.getValue();

        //把逗号分隔的字符串转成 Long 类型的列表。
        List<Long> ids = Arrays.stream(hotCity.split(",")) // 按逗号分割成 ["35","108","234","236","289","342",""]
                .map(String::trim)                         // 去掉每个元素两边空格
                .filter(s -> !s.isEmpty())                 // 只保留非空的字符串
                .flatMap(s -> {                            // 把 String 转成 Long
                    try {
                        return Stream.of(Long.valueOf(s)); // 转换成功就生成一个 Long 流
                    } catch (NumberFormatException e) {    // 转换失败（比如 "abc"）就捕获异常
                        log.warn("Invalid city id in sys_hot_city config: {}", s);
                        return Stream.empty();             // 返回空流，等于丢掉这个错误值
                    }
                })
                .toList();


        List<SysRegionDTO> result = new ArrayList<>();
        for(SysRegion sysRegion : regionMapper.selectBatchIds(ids)){
            SysRegionDTO dto = new SysRegionDTO();
            BeanCopyUtil.copyProperties(sysRegion, dto);
            result.add(dto);
        }
        CacheUtil.setL2Cache(redisService,MapConstants.CACHE_MAP_HOT_CITY,result,caffeineCache,120L, TimeUnit.SECONDS);
        return result;
    }


    /**
     * 根据地点搜索
     * @param placeSearchReqDTO 搜索条件
     * @return 搜索结果
     */
    @Override
    public BasePageDTO<SearchPoiDTO> searchSuggestOnMap(PlaceSearchReqDTO placeSearchReqDTO) {
        // 1 构造查询 腾讯位置服务API 的入参
        SuggestSearchDTO suggestSearchDTO = new SuggestSearchDTO();
        BeanCopyUtil.copyProperties(placeSearchReqDTO, suggestSearchDTO);
        suggestSearchDTO.setPageIndex(placeSearchReqDTO.getPageNo());
        //对象类型转化
        suggestSearchDTO.setId(String.valueOf(placeSearchReqDTO.getId()));
        // 2 调用腾讯地图位置接口
        PoiListDTO poiListDTO = mapProvider.searchQQMapPlaceByRegion(suggestSearchDTO);
        // 3 结果对象转换，取出了腾讯返回的 POI 列表, 把腾讯对象转化为自己的业务对象
        List<PoiDTO> poiDTOList = poiListDTO.getData();
        BasePageDTO<SearchPoiDTO> result = new BasePageDTO<>();
        result.setTotals(poiListDTO.getCount());
        result.setTotalPages(PageUtil.getTotalPages(result.getTotals(),placeSearchReqDTO.getPageSize()));
        List<SearchPoiDTO> pageRes = new ArrayList<>();
        for (PoiDTO poiDTO : poiDTOList) {
            SearchPoiDTO searchPoiDTO = new SearchPoiDTO();
            BeanCopyUtil.copyProperties(poiDTO, searchPoiDTO);
            searchPoiDTO.setLongitude(poiDTO.getLocation().getLng());
            searchPoiDTO.setLatitude(poiDTO.getLocation().getLat());
            pageRes.add(searchPoiDTO);
        }
        result.setList(pageRes);
        return result;
    }

    /**
     * 根据经纬度来定位城市
     * @param locationReqDTO 经纬度信息
     * @return 城市信息
     */
    @Override
    public RegionCityDTO getCityByLocation(LocationReqDTO locationReqDTO) {
        // 1 构造请求参数
        LocationDTO locationDTO = new LocationDTO();
        RegionCityDTO result = new RegionCityDTO();
        BeanCopyUtil.copyProperties(locationReqDTO, locationDTO);
        // 2 调用腾讯服务接口，并返回结果
        GeoResultDTO geoResultDTO = mapProvider.getQQMapDistrictByLonLat(locationDTO);
        if(geoResultDTO != null && geoResultDTO.getResult()!=null && geoResultDTO.getResult().getAd_info() != null){
            String cityName = geoResultDTO.getResult().getAd_info().getCity();
            List<SysRegionDTO> cache = CacheUtil.getL2Cache(redisService,MapConstants.CACHE_MAP_CITY_KEY
                    ,new TypeReference<List<SysRegionDTO>>() {},caffeineCache);

            for (SysRegionDTO sysRegionDTO : cache) {
                if(sysRegionDTO.getFullName().equals(cityName)){
                    BeanCopyUtil.copyProperties(sysRegionDTO, result);
                    return result;
                }
            }
        }

        return result;
    }

}



